/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.leadiv.ai.minesweeper;

import java.io.IOException;
import java.util.Vector;
import com.leadiv.ai.minesweeper.util.C2DMatrix;
import com.leadiv.ai.minesweeper.util.Params;
import com.leadiv.ai.minesweeper.util.SPoint;
import com.leadiv.ai.minesweeper.util.SVector2D;
import com.leadiv.ai.minesweeper.util.Utils;
import com.leadiv.ai.neuralnet.CNeuralNet;
import org.xml.sax.SAXException;

/**
 *
 * @author pborrego
 */
public class CMinesweeper {
    //the minesweeper's neural net
    private CNeuralNet m_ItsBrain;

    //its position in the world
    private SVector2D m_vPosition;

    //direction sweeper is facing
    private SVector2D m_vLookAt;

    //its rotation (surprise surprise)
    private double m_dRotation;

    private double m_dSpeed;

    //to store output from the ANN
    private double m_lTrack, m_rTrack;

    //the sweeper's fitness score 
    private double m_dFitness;

    //the scale of the sweeper when drawn
    private double m_dScale;

    //index position of closest mine
    private int m_iClosestMine;
    
    private Params p;
    
    //-----------------------------------constructor-------------------------
    //
    //-----------------------------------------------------------------------
    public CMinesweeper() throws SAXException, IOException
    {
        p = new Params();
        
        m_dRotation = Utils.RandFloat() * p.dTwoPi;
        m_lTrack = 0.16;
        m_rTrack = 0.16;
        m_dFitness = 0;
        m_dScale = p.iSweeperScale;
        m_iClosestMine = 0;
        
        //create a random start position
        m_vPosition = new SVector2D((Utils.RandFloat() * p.iWindowWidth), (Utils.RandFloat() * p.iWindowHeight));

    }

    //-------------------------------Update()--------------------------------
    //
    //	First we take sensor readings and feed these into the sweepers brain.
    //
    //	The inputs are:
    //	
    //	A vector to the closest mine (x, y)
    //	The sweepers 'look at' vector (x, y)
    //
    //	We receive two outputs from the brain.. lTrack & rTrack.
    //	So given a force for each track we calculate the resultant rotation 
    //	and acceleration and apply to current velocity vector.
    //
    //-----------------------------------------------------------------------
    //updates the ANN with information from the sweepers enviroment
    public boolean Update(Vector<SVector2D> mines) throws SAXException, IOException
    {
        //this will store all the inputs for the NN
        Vector<Double> inputs = new Vector<Double>();	

        //get vector to closest mine
        SVector2D vClosestMine = GetClosestMine(mines);

        //normalise it
        SVector2D.Vec2DNormalize(vClosestMine);

        //add in vector to closest mine
        inputs.add(vClosestMine.x);
        inputs.add(vClosestMine.y);

        //add in sweepers look at vector
        inputs.add(m_vLookAt.x);
        inputs.add(m_vLookAt.y);


        //update the brain and get feedback
        Vector<Double> output = m_ItsBrain.Update(inputs);

        //make sure there were no errors in calculating the 
        //output
        if (output.size() < p.iNumOutputs) 
        {
            return false;
        }

        //assign the outputs to the sweepers left & right tracks
        m_lTrack = output.get(0);
        m_rTrack = output.get(1);

        //calculate steering forces
        double RotForce = m_lTrack - m_rTrack;

        //clamp rotation
        Utils.Clamp(RotForce, -p.dMaxTurnRate, p.dMaxTurnRate);

        m_dRotation += RotForce;

        m_dSpeed = (m_lTrack + m_rTrack);	

        //update Look At 
        m_vLookAt.x = -Math.sin(m_dRotation);
        m_vLookAt.y = Math.cos(m_dRotation);

        //update position
        m_vPosition.incrementBy(SVector2D.multiply(m_vLookAt, m_dSpeed));

        //wrap around window limits
        if (m_vPosition.x > p.iWindowWidth) m_vPosition.x = 0;
        if (m_vPosition.x < 0) m_vPosition.x = p.iWindowWidth;
        if (m_vPosition.y > p.iWindowHeight) m_vPosition.y = 0;
        if (m_vPosition.y < 0) m_vPosition.y = p.iWindowHeight;

        return true;
    }

    //---------------------WorldTransform--------------------------------
    //
    //	sets up a translation matrix for the sweeper according to its
    //  scale, rotation and position. Returns the transformed vertices.
    //-------------------------------------------------------------------
    //used to transform the sweepers vertices prior to rendering
    public void WorldTransform(Vector<SPoint> sweeper)
    {
	//create the world transformation matrix
	C2DMatrix matTransform = new C2DMatrix();
	
	//scale
	matTransform.Scale(m_dScale, m_dScale);
	
	//rotate
	matTransform.Rotate(m_dRotation);
	
	//and translate
	matTransform.Translate(m_vPosition.x, m_vPosition.y);
	
	//now transform the ships vertices
	matTransform.TransformSPoints(sweeper);
    }

    //----------------------GetClosestMine()---------------------------------
    //
    //	returns the vector from the sweeper to the closest mine
    //
    //-----------------------------------------------------------------------
    //returns a vector to the closest mine
    public SVector2D GetClosestMine(Vector<SVector2D> mines)
    {
        double closest_so_far = 99999;

        SVector2D vClosestObject = new SVector2D(0, 0);

        //cycle through mines to find closest
        for (int i=0; i<mines.size(); i++)
        {
            double len_to_object = SVector2D.Vec2DLength(SVector2D.subtract(mines.get(i), m_vPosition));

            if (len_to_object < closest_so_far)
            {
                closest_so_far = len_to_object;
                vClosestObject  = SVector2D.subtract(m_vPosition, mines.get(i));
                m_iClosestMine = i;
            }
        }

        return vClosestObject;
    }

    //----------------------------- CheckForMine -----------------------------
    //
    //  this function checks for collision with its closest mine (calculated
    //  earlier and stored in m_iClosestMine)
    //-----------------------------------------------------------------------
    //checks to see if the minesweeper has 'collected' a mine
    public int CheckForMine(Vector<SVector2D> mines, double size)
    {
        SVector2D DistToObject = SVector2D.subtract(m_vPosition, mines.get(m_iClosestMine));

        if (SVector2D.Vec2DLength(DistToObject) < (size + 5))
        {
            return m_iClosestMine;
        }

        return -1;
    }

    //-------------------------------------------Reset()--------------------
    //
    //	Resets the sweepers position, fitness and rotation
    //
    //----------------------------------------------------------------------
    public void Reset()
    {
        //reset the sweepers positions
        m_vPosition = new SVector2D((Utils.RandFloat() * p.iWindowWidth), (Utils.RandFloat() * p.iWindowHeight));

        //and the fitness
        m_dFitness = 0;

        //and the rotation
        m_dRotation = Utils.RandFloat()*p.dTwoPi;
    }


    //-------------------accessor functions
    public SVector2D Position()
    {
        return m_vPosition;
    }

    public void IncrementFitness()
    {
        ++m_dFitness;
    }

    public double Fitness()
    {
        return m_dFitness;
    }

    public void PutWeights(Vector<Double> w)
    {
        m_ItsBrain.PutWeights(w);
    }

    public int GetNumberOfWeights() 
    {
        return m_ItsBrain.GetNumberOfWeights();
    }
}
